﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Wimm.Common.Entities;
using Wimm.Common.Interfaces.Repositories;
using Wimm.Common.Interfaces.Services;
using Wimm.Common.Interfaces.Utils;

namespace Wimm.BusinessLogic.Services
{
    public class IncomeService : IIncomeService
    {
        private readonly IIncomeRepository _incomeRepo;
        private readonly IIconRepository _iconsRepo;

        public IncomeService(IIncomeRepository incomeRepo, IIconRepository iconsRepo)
        {
            if (incomeRepo == null)
                throw new ArgumentNullException("incomeRepo");

            if (iconsRepo == null)
                throw new ArgumentNullException("iconsRepo");

            _incomeRepo = incomeRepo;
            _iconsRepo  = iconsRepo;
        }

        public async Task<IEnumerable<IncomeSource>> LoadSources(int? parentId)
        {
            return await _incomeRepo.LoadAllSources(parentId);
        }

        public async Task<IReadOnlyDictionary<int, Icon>> LoadSourceIcons()
        {
            Dictionary<int, Icon> res = new Dictionary<int, Icon>();

            IEnumerable<Icon> iconsFromDb = await _iconsRepo.LoadAllIcons();

            foreach (Icon i in iconsFromDb)
                res.Add(i.Id, i);

            return res;
        }

        public async Task<IPagedResult<Income>> LoadPaged(IPagedRequest<Income> request)
        {
            return await _incomeRepo.LoadPaged(request);
        }

        public async Task<Income> InsertIncome(Income newIncome)
        {
            return await _incomeRepo.InsertIncome(newIncome);
        }
        
        public async Task<Income> EditIncome(Income income)
        {
            return await _incomeRepo.EditIncome(income);
        }

        public async Task<IEnumerable<Tuple<int, int, int, string>>> LoadCategories()
        {
            var result = new List<Tuple<int, int, int, string>>(); // id, parent id, nesting lvl, name
            var categories = await _incomeRepo.LoadCategories();
            result.AddRange(categories.Where(x => x.ParentId == null).Select(x => new Tuple<int, int, int, string>(x.Id, 0, 0, x.Description)));

            for (int i = 0; i < result.Count; i++)
            {
                var index = result.IndexOf(result[i]);
                var itemChild = categories.Where(x => x.ParentId == result[i].Item1);
                if (itemChild.Count() > 0)
                {
                    result.InsertRange(index + 1, itemChild.Select(x =>
                        new Tuple<int, int, int, string>(
                            x.Id,
                            result[i].Item1,
                            result[i].Item3 + 1,
                            x.Description)
                        ));
                }
            }

            return result;
        }

        public async Task<IEnumerable<KeyValuePair<IncomeSource, decimal>>> LoadIncomeGroups(DateTime? from, DateTime? to, int userId, bool detailed)
        {
            IReadOnlyCollection<CategoryGroup> incomesGroup = await _incomeRepo.LoadGrouped(from, to, userId);
            Dictionary<IncomeSource, decimal> totalIncome = new Dictionary<IncomeSource, decimal>();

            if (detailed)
            {
                foreach (CategoryGroup eg in incomesGroup)
                {
                    IncomeSource c = await _incomeRepo.LoadCategoryById(eg.CategoryId);
                    totalIncome.Add(c, eg.Amount);
                }
            }
            else
            {
                foreach (CategoryGroup eg in incomesGroup)
                {
                    IncomeSource c = await _incomeRepo.LoadRootCategory(eg.CategoryId);
                    if (totalIncome.ContainsKey(c))
                        totalIncome[c] = totalIncome[c] + eg.Amount;
                    else
                        totalIncome.Add(c, eg.Amount);
                }
            }

            return totalIncome;
        }


        public async Task<DateTime> GetLimitDate(bool isMaxDate)
        {
            return await _incomeRepo.GetLimitDate(isMaxDate);
        }
    }
}
